/*
 * "This optimization strategy is based on a method implemented in the FREVO (Framework for Evolutionary Design) tool. See http://www.frevotool.tk/ for more details."
 * Copyright (C) 2010 Wilfried Elmenreich
 * This file is part of VideoNetwork.
 * 
 * VideoNetwork is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 
 * You should have received a copy of the GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. 
 */
package simulation;

import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Random;

import proxy.HormoneControls;


public class Optimizer {
	int nnodes = 50;
	int evaluations = 5;
	double simTime = 200.0;
	Random rand = new Random(1234);
	double mutationAbsolute = 0.01;
	double mutationRelative = 0.1;
	int populationSize = 100;
	double maxEvolveTimeInSeconds = 500000.0;
	double Nelite = .32;
	double Nmutate = .32;
	double Nxover = .36;
	int generation = 0;
	int fitnessFunction;

	static final int noOfDoubleParamters = 8;

	class Genom implements Comparable<Genom> {
		Double x[] = new Double[noOfDoubleParamters];
		double fitness = 0.0;
		int isEvaluated;
		HormoneControls ctrls;

		Genom() {
			for (int i = 0; i < noOfDoubleParamters; i++)
				x[i] = rand.nextDouble() * 2.0;
			ctrls = new HormoneControls(x);
			fitness = 0.0;
			isEvaluated = 0;

		}

		double evaluate(int evalruns) {
			if (isEvaluated >= evalruns)
				return fitness;
			double sumFitness = fitness * isEvaluated;

			for (int i = isEvaluated; i < evalruns; i++) {
				SimulatorConfig cfg = new SimulatorConfig(nnodes);
				cfg.setRand(i);
				cfg.setLogActivate(false);
				Simulator simulator = new Simulator(cfg, null, ctrls);
				simulator.updateFavorites();
				simulator.processViewings();
				simulator.advance(simTime);

				switch (fitnessFunction) {

				case 1:
					sumFitness += simulator.deadlineStat.n / simTime; // fitness is
					// throughput
					// of videos
					break;
				case 2:
					sumFitness += simulator.delayStat.n / 1000.0
							- simulator.longestWaitingTime(100) / simTime; // fitness
					// based
					// on
					// maxdelaytimes
					break;
				case 3:
				    	sumFitness+= simulator.deadlineStat.n - simulator.migrationMoveCounter;
					break;
				
				default:
					System.err.println("No fitness function selected!");
					System.exit(1);
				}
			}
			fitness = sumFitness / evalruns; // average over all evaluations

			isEvaluated = evalruns;
			return fitness;
		}

		void randomize() {
			for (int i = 0; i < noOfDoubleParamters; i++)
				x[i] = rand.nextDouble();
			ctrls = new HormoneControls(x);
			fitness = 0.0;
			isEvaluated = 0;
		}

		void mutate() {
			for (int i = 0; i < noOfDoubleParamters; i++) {
				double mutationRate = mutationAbsolute + mutationRelative
						* x[i];
				x[i] += rand_range(mutationRate);
				if (x[i] < 0.0)
					x[i] = 0.0;
			}
			ctrls = new HormoneControls(x);
			isEvaluated = 0;
		}

		void XoverWith(Genom g2) {
			for (int i = 0; i < noOfDoubleParamters; i++)
				if (rand.nextBoolean())
					x[i] = g2.x[i];
			ctrls = new HormoneControls(x);
			isEvaluated = 0;
		}

		void copyFrom(Genom g2) {
			for (int i = 0; i < noOfDoubleParamters; i++)
				x[i] = g2.x[i];
			ctrls = new HormoneControls(x);
			fitness = g2.fitness;
			isEvaluated = g2.isEvaluated;
		}

		String result() {
			DecimalFormat fm = new DecimalFormat("0.00");
			String s = "";
			String delim = "";
			for (int i = 0; i < noOfDoubleParamters; i++) {
				s = s + delim + fm.format(x[i]);
				delim = " , ";
			}
			return s + " Fitness:" + fitness;
		}

		private double rand_range(double border) {
			return rand.nextDouble() * 2 * border - border;
		}

		public int compareTo(Genom o) {
			if (fitness > o.fitness)
				return 1;
			if (fitness < o.fitness)
				return -1;
			return 0;
		}
	}

	//TODO make readable
	public void evolve(int maxGenerations, int fitnessFunction) {
		ArrayList<Genom> population = new ArrayList<Genom>();
		double runTime = 0.0, startTime = System.currentTimeMillis();

		this.fitnessFunction = fitnessFunction;
		// fill initally with random genomes
		for (int i = 0; i < populationSize; i++)
			population.add(new Genom());

		for (generation = 0; generation < maxGenerations; generation++) {
			// evaluate
			for (Genom g : population)
				if (generation / 2 + 1 < evaluations) // in the early phase high
					// accuracy is not
					// needed
					g.evaluate(generation / 2 + 1);
				else
					g.evaluate(evaluations);

			// sort according to fitness, best one comes first
			Collections.sort(population, Collections.reverseOrder());

			int ind;
			// elite selection: keep first Nelite genoms
			ind = (int) (Nelite * populationSize);
			// mutations
			for (int i = ind; i < ind + Nmutate * populationSize; i++) {
				population.get(i).copyFrom(population.get(rand.nextInt(ind)));
				population.get(i).mutate();
			}

			ind += Nmutate * populationSize;
			// Xovers
			for (int i = ind; i < ind + Nxover * populationSize; i++) {
				population.get(i).copyFrom(population.get(rand.nextInt(ind)));
				population.get(i).XoverWith(population.get(rand.nextInt(ind)));
			}
			ind += Nxover * populationSize;
			// and new random genoms
			for (int i = ind; i < populationSize; i++)
				population.get(i).randomize();

			// for(Genom g:population)
			// System.out.println("Gen:"+generation+" "+g.result());
			System.out.println("Gen" + generation + ": Best:"
					+ population.get(0).result());
			System.out.println();
			runTime = (System.currentTimeMillis() - startTime) / 1000.0;
			if (runTime > maxEvolveTimeInSeconds) {
				System.out.println("Time has elapsed!");
				break;
			}
		}
		System.out.println("\nGenetic Algorithm has finished after "
				+ (int) runTime + " seconds!");
		System.out.println("Best result:" + population.get(0).result());
	}
}
